package org.yuezejian.alarm.netty.handler;

import org.yuezejian.alarm.netty.MultiNettyClientManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

/**
 * 抽象客户端处理器，封装了心跳包发送、重连机制等功能
 *
 * @author yuezejian
 * @since 2024.8.1
 */
@Slf4j
public abstract class AbstractClientHandler extends SimpleChannelInboundHandler<String> {

    @Autowired
    private MultiNettyClientManager multiNettyClientManager;

    protected int maxReconnectAttempts = 2147483647; // 最大重连次数
    protected int initialReconnectDelay = 1000; // 初始重连延迟（毫秒）
    private ThreadLocal<Integer> currentReconnectAttempts = ThreadLocal.withInitial(() -> 0); // 当前重连次数
    private Thread heartbeatThread; // 心跳线程
    private volatile boolean running = true; // 控制心跳线程的标志

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        startHeartbeat(ctx); // 在通道激活时启动心跳线程
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        running = false; // 停止心跳线程
        reconnect(ctx); // 通道失效时尝试重连
    }

    protected void reconnect(ChannelHandlerContext ctx) {
        int attempts = currentReconnectAttempts.get(); // 获取当前重连次数
        if (attempts < maxReconnectAttempts) {
            currentReconnectAttempts.set(attempts + 1); // 增加当前重连次数
            int reconnectDelay = initialReconnectDelay * currentReconnectAttempts.get(); // 每次重连的延迟时间逐渐增加
            if (attempts >= 20) {
                initialReconnectDelay = 60000;
                reconnectDelay = initialReconnectDelay * currentReconnectAttempts.get()-19; // 每次重连的延迟时间逐渐增加
            }
            // 60次一轮
            if (attempts == 40) {
                currentReconnectAttempts.set(1);
            }
            ctx.channel().eventLoop().schedule(() -> {
                log.info("Attempting to reconnect... Attempt " + currentReconnectAttempts.get());
                if (shouldReconnect(ctx)) {
                    doReconnect(ctx); // 调用重连方法
                } else {
                    log.info("Reconnect cancelled.");
                }
            }, reconnectDelay, TimeUnit.MILLISECONDS);
        } else {
            log.info("Max reconnect attempts reached. Giving up.");
        }
    }

    // 允许子类决定是否进行重连
    protected boolean shouldReconnect(ChannelHandlerContext ctx) {
        return true; // 默认情况下进行重连
    }

    protected void doReconnect(ChannelHandlerContext ctx) {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIP = insocket.getAddress().getHostAddress();
        int port = insocket.getPort();
        String clientId = clientIP + ":" + port;
        log.info("正在进行重连：" + clientId);
        try {
            multiNettyClientManager.acquireChannel(clientId);
            // 重连次数重新计数
            resetReconnectAttempts();
            // 重启心跳进程
            running = true;
            startHeartbeat(ctx);
        } catch (Exception e) {
            reconnect(ctx);
        }
    }// 具体重连逻辑由子类实现

    @Override
    protected abstract void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception; // 处理接收到的消息

    private void startHeartbeat(ChannelHandlerContext ctx) {
        heartbeatThread = new Thread(() -> {
            while (running) {
                try {
                    sendHeartbeat(ctx); // 发送心跳包
                    Thread.sleep(5000); // 每5秒发送一次心跳
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    break;
                }
            }
        });
        heartbeatThread.start(); // 启动心跳线程
    }

    protected void sendHeartbeat(ChannelHandlerContext ctx) {
        // 发送心跳包的逻辑
        ctx.writeAndFlush("HEARTBEAT\n");
    }

    /**
     * 子类中重连成功后调用此方法，重置重连次数
     */
    public void resetReconnectAttempts() {
        currentReconnectAttempts.set(0);
    }
}

